package com.guojian.common.scan;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.function.Consumer;

import javax.annotation.Nullable;

import org.springframework.context.annotation.Configuration;

import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ClassUtil;

public class Scan {

	private static ClassPath classPath;

	public ScanBuilder scanBuilder;

	public Scan(ScanBuilder scanBuilder) {
		this.scanBuilder = scanBuilder;
	}

	public FluentIterable<ClassInfo> scan() {
		FluentIterable<ClassInfo> it = FluentIterable.from(classPath.getResources()).filter(ClassInfo.class);
		for (Predicate<ClassInfo> pclassInfo : scanBuilder.predicateList) {
			it = it.filter(pclassInfo);
		}
		return it;
	}

	public List<Class<?>> getClassList() {
		List<Class<?>> scanClasses = Lists.newArrayList();

		scan().toList().forEach(new Consumer<ClassInfo>() {
			@Override
			public void accept(ClassInfo t) {
				try {
					scanClasses.add(Class.forName(t.getName()));
				} catch (ClassNotFoundException e) {
					//
				}
			}
		});

		return scanClasses;
	}

	static {
		try {
			classPath = ClassPath.from(ClassUtil.getClassLoader());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static ScanBuilder getScanBuilder() {
		return new ScanBuilder();
	}

	public static ScanBuilder getScanBuilder(String basePackages) {
		return new ScanBuilder(basePackages);
	}

	static class ScanBuilder {

		private List<Class<? extends Annotation>> annotationList = Lists.newArrayList();

		private List<String> basePackages = Lists.newArrayList();

		private boolean isTopLevelClass = true;

		private List<Predicate<ClassInfo>> predicateList = Lists.newArrayList();

		public ScanBuilder() {

		}

		public ScanBuilder(String basePackage) {
			this.basePackages.add(basePackage);
		}

		public ScanBuilder addBasePackage(String basePackage) {
			this.basePackages.add(basePackage);
			return this;
		}

		public ScanBuilder addAnnotation(Class<? extends Annotation> annotation) {
			this.annotationList.add(annotation);
			return this;
		}

		public ScanBuilder setTopLevelClass(boolean isTopLevelClass) {
			this.isTopLevelClass = isTopLevelClass;
			return this;
		}

		public ScanBuilder addPredicate(Predicate<ClassInfo> p) {
			this.predicateList.add(p);
			return this;
		}

		public List<Class<? extends Annotation>> getAnnotationList() {
			return annotationList;
		}

		public List<String> getBasePackages() {
			return basePackages;
		}

		public boolean isTopLevelClass() {
			return isTopLevelClass;
		}

		public Scan build() {

			Predicate<ClassInfo> exclutePredicate = new Predicate<ClassInfo>() {
				@Override
				public boolean apply(@Nullable ClassInfo input) {
					Assert.notNull(input);
					return input.getName().indexOf("jdk") == -1 && input.getName().indexOf("sun") == -1 && input.getName().indexOf("java") == -1;
				}
			};

			addPredicate(exclutePredicate);

			//basePackage的过滤放在第一位,可以减少加载的class,因为annotation的过滤需要Class.forName
			for (String basePackage : getBasePackages()) {
				Predicate<ClassInfo> packagePredicate = new Predicate<ClassInfo>() {
					@Override
					public boolean apply(@Nullable ClassInfo input) {
						Assert.notNull(input);
						return input.getName().startsWith(basePackage);
					}
				};
				addPredicate(packagePredicate);
			}

			if (isTopLevelClass()) {
				Predicate<ClassInfo> isTopLevel = new Predicate<ClassInfo>() {
					@Override
					public boolean apply(ClassInfo info) {
						Assert.notNull(info);
						return info.getName().indexOf('$') == -1;
					}
				};
				addPredicate(isTopLevel);
			}

			for (Class<? extends Annotation> annotation : getAnnotationList()) {
				Predicate<ClassInfo> annotationPredicate = new Predicate<ClassInfo>() {
					@Override
					public boolean apply(@Nullable ClassInfo input) {
						Assert.notNull(input);
						try {
							Class<?> c = Class.forName(input.getName());
							return c.isAnnotationPresent(annotation);
						} catch (Exception e) {
							//no
						}
						return false;
					}
				};
				addPredicate(annotationPredicate);
			}

			return new Scan(this);
		}

	}

	public static void main(String[] args) {

		/*ImmutableList<ClassInfo> classInfoList = Scan.getScanBuilder().setBasePackages("guojian").build().scan().toList();
		
		for (ClassInfo classInfo : classInfoList) {
			System.out.println(classInfo.getName());
		}*/

		ImmutableList<ClassInfo> scanClasses = Scan.getScanBuilder().addBasePackage("com.guojian").addAnnotation(Configuration.class).build().scan().toList();

		for (ClassInfo class1 : scanClasses) {
			System.out.println(class1.getName());
		}

	}
}
